-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

-- Description: vrd升级
local singleton = require 'mc.singleton'
local skynet = require 'skynet'
local log = require 'mc.logging'
local fw_mgmt = require 'general_hardware.client'
local parser_cfg = require 'mcu.upgrade.parser_cfg'
local vos = require 'utils.vos'
local defs = require 'mcu.upgrade.defs'
local utils = require 'mc.utils'
local fructl = require 'mcu.upgrade.fructl_handler'
local context = require 'mc.context'
local cmn = require 'common'
local utils_core = require 'utils.core'
local fw_vrd_valid = require 'mcu.upgrade.valid'
local upgrade_service_comm = require 'mcu.upgrade.upgrade_service.upgrade_service_comm'

local upgrade_service_vrd = {}
upgrade_service_vrd.__index = upgrade_service_vrd

local LOCK_FOREVER <const> = 0xffff

local vrd_active_register_list = {
    {Key = 'FirmwareId', Value = 'VRD'},
    {Key = 'FirmwareType', Value = 'VRD'},
    {Key = 'ActiveCondition', Value = 'PowerOff'},
    {Key = 'ActiveMode', Value = 'None'},
    {Key = 'ActiveStatus', Value = 'Idle'}
}

function upgrade_service_vrd.new(bus, db, mcu_collection)
    return setmetatable({
        bus = bus,
        db = db,
        is_vrd_upgrading = false,
        mcu_collection = mcu_collection,
        upgrade_list = {},
        hpm_path = "",
        cfgs = {},
        power_state_list = {},
        upgrade_final_ret = defs.RET.OK,
        poweron_lock_list = {},
        is_vrd_cached = {},
        before_upgrade_version = ""
    }, upgrade_service_vrd)
end

-- 更新电源状态列表
local function update_power_state_list(self)
    local sys_id
    self.power_state_list = {}
    for _, obj in pairs(self.mcu_collection) do
        sys_id = obj:get_system_id()
        if self.power_state_list[sys_id] == nil then
            self.power_state_list[sys_id] = fructl.get_power_status(self.bus, sys_id)
        end
    end
end

-- 更新VRD正在升级标记
local function update_vrd_upgrading_flag(self, system_id)
    -- 升级所有Host，只要存在一个Host下电时更新为正在升级
    if system_id == defs.ALL_SYSTEM_ID then
        for _, power_state in pairs(self.power_state_list) do
            if power_state == 'OFF' then
                self.is_vrd_upgrading = true
                break
            end
        end
    -- 升级单Host且下电时更新为正在升级
    elseif self.power_state_list[system_id] == 'OFF' then
        self.is_vrd_upgrading = true
    end

    log:notice('[Vrd] set vrd upgrading flag to %s', self.is_vrd_upgrading)
end

-- VRD升级对下电的Host增加上电锁
local function vrd_upgrade_poweron_lock(self)
    local sys_id
    for _, upgrade_obj in pairs(self.upgrade_list) do
        sys_id = upgrade_obj:get_system_id()
        -- VRD下电升级且还未加上电锁的host，需加上电锁
        if self.power_state_list[sys_id] == 'OFF' and not self.poweron_lock_list[sys_id] then
            -- 当前VRD升级时间没有统一规格，有的机型升级时间甚至超过2小时，暂加永久锁;加锁后hardware异常重启需复位BMC解锁
            fructl.set_poweron_lock_until_success(self.bus, sys_id, true, LOCK_FOREVER, defs.VRD_NAME)
            self.poweron_lock_list[sys_id] = true
        end
    end
end

-- 获取VRD升级当前版本号
local function vrd_get_upgrade_cur_version(system_id, upgrade_list)
    if not next(upgrade_list) or system_id == defs.ALL_SYSTEM_ID then
        -- system_id为255时，不获取版本号
        log:error('[Vrd] Can not find int cfgs')
        return ''
    end
    local vrd_version = {}
    for _, upgrade_obj in pairs(upgrade_list) do
        if upgrade_obj:get_system_id() ~= system_id then
            goto continue
        end
        local component = upgrade_obj:get_ref_subcomp()
        if not component then
            goto continue
        end
        local ok, version = pcall(component.ge_component_major_version, component)
        if ok and version and version ~= "" then
            table.insert(vrd_version, version)
        end
        ::continue::
    end
    if #vrd_version ~= 0 then
        return table.concat(vrd_version, '.')
    end
    log:error("[Vrd] get before version failed")
    return ""
end

local function prepare_get_vrd_version(self, version, system_id)
    -- system_id为255时，不获取版本号
    if system_id == defs.ALL_SYSTEM_ID then
        return version
    end

    -- VRD返回全255的话取资源树上的version显示
    local all_default = true
    for v in string.gmatch(version, "%d+") do
        if v ~= "" and tonumber(v) ~= 255 then
            all_default = false
            break
        end
    end
    if not all_default then
        return version
    end

    for _, upgrade_obj in pairs(self.upgrade_list) do
        local component = upgrade_obj:get_ref_subcomp()
        local tree_version = component.get_mdb_vrd_firmware_version(self.bus, upgrade_obj:get_ref_mcu())
        log:notice('[Vrd] tree version is %s', tree_version)
        if tree_version then
            version = tree_version
        end
        -- 当前资源树显示八个chip，只需获取一次
        break
    end
    return version
end

-- VRD缓存更新升级详细信息
local function vrd_cache_update_upg_details(upg_index, upg_details)
    log:debug("[Vrd] vrd(firmware index:%s) cached", upg_index)
    upg_details.upgraded_cnt = upg_details.upgraded_cnt + 1
    upg_details.prog_stats_i:set_sub_task_progress(upg_index, 100)
end

-- 根据system_id缓存VRD生效文件；调用者需保证system_id有效性
local function cache_upgrade_file(hpm_path, system_id)
    if not hpm_path or not vos.get_file_accessible(hpm_path)  then
        log:error('[Vrd] cache_upgrade_file: hpm path is no accessible.')
        return defs.RET.ERR
    end

    -- mkdir -p 多级创建目录
    if not vos.get_file_accessible(defs.UPGRADE_VRD_VALID_FILE_DIR) then
        os.execute('mkdir -p ' .. defs.UPGRADE_VRD_VALID_FILE_DIR)
    end

    local target_path = string.format('%sHOST_%s_%s', defs.UPGRADE_VRD_VALID_FILE_DIR, system_id, defs.VRD_HPM)
    local ok, err = pcall(utils.copy_file, hpm_path, target_path)
    if not ok then
        log:error('[Vrd] cache system_id(%s) vrd hpm failed, err(%s)', system_id, err)
        return
    end
    log:notice('[Vrd] cache system_id(%s) vrd hpm successfully', system_id)
end

local function cache_vrd(obj, is_vrd_cached, hpm_path)
    if obj.power_state ~= 'OFF' then
        if not is_vrd_cached[obj.system_id] then
            cache_upgrade_file(hpm_path, obj.system_id)
            is_vrd_cached[obj.system_id] = true
        end
        return true
    end
    return false
end

local function is_upgrade_version(version)
    local vrd_list = {}
    for v in string.gmatch(version, "%d+") do
        table.insert(vrd_list, v)
    end
    if next(vrd_list) then
        for _, v in pairs(vrd_list) do
            if v ~= vrd_list[1] then
                return false
            end
        end
        return true
    end
    return false
end

local function vrd_update_version(self, system_id, firmware_type, upg_ret_code)
    -- 统一更新VRD版本号,最多重试10次
    cmn.skynet.fork(function ()
        local version, retry = '', 0
        for _ = 1, 10 do
            upgrade_service_comm.update_vrd_component_version(self.bus, self.mcu_collection, system_id)
            if system_id == defs.ALL_SYSTEM_ID then
                return
            end
            -- 记录升级后版本
            version = vrd_get_upgrade_cur_version(system_id, self.upgrade_list)
            if is_upgrade_version(version) then
                break
            end
            -- 等待10s后重新更新
            cmn.skynet.sleep(1000)
            retry = retry + 1
        end
        log:notice('[Vrd] upgrade new version is %s, retry %s', version, retry)
        -- 记录升级维护日志
        upgrade_service_comm.record_upgrade_maint_log(firmware_type, upg_ret_code, version, self.before_upgrade_version)
    end)
end

local function paraller_upgrade_fw(self, upgrade_details, parameters)
    log:notice("[Vrd] paraller upgrade vrd start")
    local ok, ret_code
    local upg_obj_info, upg_obj, upg_index
    while true do
        -- 获取当前要处理的升级对象
        upg_obj_info, upg_obj, upg_index = upgrade_service_comm.get_cur_upgrade_obj_info(upgrade_details)
        if not upg_obj_info then
            log:debug("[Vrd] dont upgrade obj info to upgrade")
            goto continue
        end

        log:debug("[Vrd] get upgrade obj info to upgrade, upgrade index:%s", upg_index)
        -- VRD缓存并更新升级详细信息
        if cache_vrd(upg_obj, self.is_vrd_cached, self.hpm_path) then
            vrd_cache_update_upg_details(upg_index, upgrade_details)
            goto continue
        end

        -- 升级加锁处理
        ok, ret_code = upg_obj:chip_lock(require 'mc.context'.new(), 1800)
        if not ok or ret_code ~= 0 then
            log:debug("[Vrd] upgrade index:%s set lock status to 1 failed, ret code:%s", upg_index, ret_code)
            -- 加锁失败，放到队尾等待
            table.insert(upgrade_details.upgrade_list, upg_obj_info)
            goto continue
        end

        -- 开始执行升级
        skynet.fork_once(function ()
            upgrade_service_comm.upgrade_one_fw(self.bus, upg_obj_info, upgrade_details)
        end)

        ::continue::
        skynet.sleep(100)
        -- progress过程进度从20%到85%
        fw_mgmt:UpdateServiceUpdateServiceUpdateUpgradeStatus(context.new(), upgrade_details.system_id,
            upgrade_details.firmware_type, 0, upgrade_details.prog_stats_i:get_current_progress(), 'Running',
            parameters)

        -- 所有升级对象处理完成
        if upgrade_service_comm.is_upgrade_obj_proc_finish(upgrade_details) then
            break
        end
    end
    log:notice("[Vrd] paraller upgrade vrd end")
end

local function serial_upgrade_fw(self, upgrade_details, parameters)
    log:notice("[Vrd] serial upgrade vrd")
    -- 更新串行升级任务进度
    upgrade_service_comm.update_serial_upgrade_progress(upgrade_details, parameters)

    for _, upg_obj_info in pairs(upgrade_details.upgrade_list) do
        -- VRD固件所在host非下电状态，则不升级并缓存升级包
        if cache_vrd(upg_obj_info.upgrade_obj, self.is_vrd_cached, self.hpm_path)  then
            vrd_cache_update_upg_details(upg_obj_info.upgrade_index, upgrade_details)
        else
            upgrade_service_comm.upgrade_one_fw(self.bus, upg_obj_info, upgrade_details)
        end
    end
end

local function vrd_upgrade_prepare(self, system_id, firmware_type, cfg_path, hpm_path)
    -- 更新所有Host电源状态列表
    update_power_state_list(self)

    -- 更新VRD正在升级标记
    update_vrd_upgrading_flag(self, system_id)

    -- 保存升级信息
    local _ , cfgs = parser_cfg.get_cfgs(cfg_path)
    self.cfgs = cfgs
    self.hpm_path = hpm_path

    local version
    for _, cfg in pairs(self.cfgs) do
        -- 获取升级列表
        self.upgrade_list = upgrade_service_comm.get_upgrade_list(self.bus, self.mcu_collection, cfg,
            firmware_type, system_id)
        -- VRD升级加上电锁
        vrd_upgrade_poweron_lock(self)
        -- 获取VRD升级前版本号
        version = vrd_get_upgrade_cur_version(system_id, self.upgrade_list)
        if version ~= '' then
            break
        end
    end
    self.before_upgrade_version = prepare_get_vrd_version(self, version, system_id)
    log:notice('[Vrd] the system_id(%s) before upgrade version is %s', system_id, self.before_upgrade_version)

    if upgrade_service_comm.is_component_concerned(self.cfgs, firmware_type) ~= defs.RET.OK then
        self.cfgs = {}
        self.is_vrd_upgrading = false
        log:error('[Vrd] Prepare vrd upgrade failed')
        return defs.RET.ERR, self.before_upgrade_version
    end

    log:notice('[Vrd] Prepare vrd upgrade finish')
    return defs.RET.OK, self.before_upgrade_version
end

local function vrd_upgrade_task(self, system_id, firmware_type, file_path, parameters)
    -- 解析出升级目录和文件名
    local dir, firmware_name = parser_cfg.parse_dir(file_path)

    -- 解析出文件路径对应的cfg配置文件，并获取升级列表
    self.upgrade_list = upgrade_service_comm.get_upgrade_list(self.bus, self.mcu_collection, self.cfgs[firmware_name],
        firmware_type, system_id)
    if not next(self.upgrade_list) then
        log:error('[Vrd] No fw need to upgrade during process stage')
        return defs.RET.ERR
    end

    -- 针对prepare阶段和process阶段上下电状态不一致的场景，避免写入固件时未加上电锁
    vrd_upgrade_poweron_lock(self)

    fw_mgmt:UpdateServiceUpdateServiceUpdateUpgradeStatus(context.new(), system_id, firmware_type, 0, 20, 'Running',
        parameters)

    -- 解压固件，最大解压限制100M
    utils.secure_tar_unzip(file_path, dir, 0x6400000, 1024)

    -- VRD缓存hpm包记录表，且同一个host只缓存一次hpm包
    self.is_vrd_cached = {}

    -- 填充升级详细信息
    local upgrade_details = upgrade_service_comm.upgrade_fill_details(system_id, firmware_type, dir, self.upgrade_list)

    -- 升级处理
    if self.upgrade_list[1]:chip_lock_supported() then
        paraller_upgrade_fw(self, upgrade_details, parameters)
    else
        serial_upgrade_fw(self, upgrade_details, parameters)
    end

    local upg_ret_code = upgrade_details.upgrade_ret_code
    vrd_update_version(self, system_id, firmware_type, upg_ret_code)

    -- 更新升级进度
    if upg_ret_code == defs.RET.OK then
        log:notice("[Vrd] upgrade process finish")
        fw_mgmt:UpdateServiceUpdateServiceUpdateUpgradeStatus(context.new(), system_id, firmware_type,
            upg_ret_code, 90, 'Running', parameters)
    end

    return upg_ret_code
end

local function poweron_unlock(bus, poweron_lock_list)
    for sys_id, _ in pairs(poweron_lock_list) do
        fructl.set_poweron_lock_until_success(bus, sys_id, false, LOCK_FOREVER, defs.VRD_NAME)
    end
end

local function clear_vrd_upgrade_resource(self)
    poweron_unlock(self.bus, self.poweron_lock_list)
    self.is_vrd_upgrading = false
    self.poweron_lock_list = {}
end

local function is_need_active_vrd(is_vrd_cached)
    for k, v in pairs(is_vrd_cached) do
        if v then
            log:notice('[Vrd] system %s vrd need to active', k)
            return true
        end
    end
    log:notice('[Vrd] no vrd need to active')
    return false
end

-- VRD升级处理阶段响应
local function vrd_upgrade_process_reply(self, system_id, firmware_type, ret_code, parameters)
    -- 存在一个VRD缓存固件包，则需要等到finish阶段注册待生效资源树之后再返回最终结果，否则返回当前升级结果
    local process_stage_ret = is_need_active_vrd(self.is_vrd_cached) and defs.RET.OK or ret_code
    if process_stage_ret ~= defs.RET.OK then
        clear_vrd_upgrade_resource(self)
        log:error('[Vrd] upgrade process failed, firmware_type:%s, ret_code: %s', firmware_type, ret_code)
    end
    fw_mgmt:UpdateServiceUpdateServiceProcessReply(context.new(), system_id, firmware_type, process_stage_ret,
        parameters)
end

local function is_vrd(cfg, firmware_type)
    if firmware_type == "Vrd" and cfg.component_id == defs.convert_component[firmware_type].id and
        cfg.component_idex == defs.convert_component[firmware_type].idex then
        return true
    end
    return false
end

local function save_db_cfg(self, firmware_type)
    for _, cfg in pairs(self.cfgs) do
        if not cfg or not cfg.uid_list or next(cfg.uid_list) == nil then
            cfg = nil
        end
        -- 保存所有cfg下FirmWare配置
        if cfg and is_vrd(cfg, firmware_type) then
            parser_cfg.save_cfg(self, cfg)
        end
    end
end

local function update_active_status(firmware_id, firmware_type, status)
    log:notice('[Vrd] update active status ')
    local param = {}
    param[#param+1] = {Key = 'FirmwareId', Value = firmware_id}
    param[#param+1] = {Key = 'FirmwareType', Value = firmware_type}
    param[#param+1] = {Key = 'ActiveStatus', Value = status}
    fw_mgmt:FirmwareActiveFirmwareActiveUpdateActiveStatus(context.new(), param)
end

-- 根据system_id注册VRD生效任务
function upgrade_service_vrd:register_vrd_active_action()
    -- 判断是否存在VRD生效缓存文件
    if not next(utils_core.dir(defs.UPGRADE_VRD_VALID_FILE_DIR)) then
        log:notice('[Vrd] no cached vrd hpm found, dont register vrd active')
        return
    end
    local active_condition = fructl.is_multihost_type(self.bus) and 'ChassisPowerOff' or 'PowerOff'
    vrd_active_register_list[3] = {Key = 'ActiveCondition', Value = active_condition}
    fw_mgmt:FirmwareActiveFirmwareActiveRegisterActiveAction(context.new(), vrd_active_register_list)
    log:notice('[Vrd] register active action successfully, ActiveCondition = %s', active_condition)
end

function upgrade_service_vrd:on_upgrade_prepare(system_id, firmware_type, cfg_path, hpm_path, parameters)
    log:notice("[Vrd] on upgrade prepare, firmware_type:%s", firmware_type)
    if not upgrade_service_comm.upgrade_file_path_chown(cfg_path, hpm_path) then
        fw_mgmt:UpdateServiceUpdateServicePrepareReply(context.new(), system_id, firmware_type, '', defs.RET.ERR,
            parameters)
        return
    end

    if self.is_vrd_upgrading then
        log:error('[Vrd] firmware %s is upgrading', firmware_type)
        fw_mgmt:UpdateServiceUpdateServicePrepareReply(context.new(), system_id, firmware_type, '',
            defs.RET.OTHERS_UPGRADING, parameters)
        return
    end

    skynet.fork_once(function ()
        local ok, ret_code, version_str = pcall(function ()
            return vrd_upgrade_prepare(self, system_id, firmware_type, cfg_path, hpm_path)
        end)
        if not ok then
            log:error('[Vrd] call vrd_upgrade_prepare failed, ret: %s', ret_code)
            ret_code = defs.RET.ERR
            version_str = ''
        end

        if ret_code ~= defs.RET.OK then
            clear_vrd_upgrade_resource(self)
            log:error('[Vrd] upgrade prepare failed, firmware_type:%s, ok:%s, ret_code: %s', firmware_type,
                tostring(ok), ret_code)
        end
        fw_mgmt:UpdateServiceUpdateServicePrepareReply(context.new(), system_id, firmware_type, version_str, ret_code,
            parameters)
    end)
end

function upgrade_service_vrd:on_upgrade_process(system_id, firmware_type, file_path, parameters)
    log:notice("[Vrd] on upgrade process, firmware_type:%s", firmware_type)
    if not next(self.cfgs) then
        clear_vrd_upgrade_resource(self)
        log:error('[Vrd] upgrade process failed, there is no cfgs, firmware_type:%s', firmware_type)
        fw_mgmt:UpdateServiceUpdateServiceProcessReply(context.new(), system_id, firmware_type, defs.RET.ERR,
            parameters)
        return
    end

    skynet.fork(function ()
        local ok, ret_code = pcall(function ()
            return vrd_upgrade_task(self, system_id, firmware_type, file_path, parameters)
        end)
        if not ok then
            log:error('[Vrd] call vrd_upgrade_task failed, ret: %s', ret_code)
            ret_code = defs.RET.ERR
        end
        -- 更新最终给用户返回的升级结果
        self.upgrade_final_ret = ret_code

        -- VRD升级处理阶段响应
        vrd_upgrade_process_reply(self, system_id, firmware_type, ret_code, parameters)
    end)
end

function upgrade_service_vrd:on_upgrade_finish(system_id, firmware_type, parameters)
    if not next(self.cfgs) then
        return
    end

    skynet.fork(function ()
        local ok, err = pcall(function ()
            upgrade_service_comm.upgrade_finish(self.upgrade_list)
        end)
        if not ok then
            clear_vrd_upgrade_resource(self)
            log:error('[Vrd] upgrade finish failed, err: %s', err)
            fw_mgmt:UpdateServiceUpdateServiceFinishReply(context.new(), system_id, firmware_type, defs.RET.ERR,
                parameters)
            return
        end

        -- VRD注册生效策略
        ok, err = pcall(function ()
            self:register_vrd_active_action()
        end)
        if not ok then
            log:notice('[Vrd] try to register active action failed, err: %s', err)
        else
            save_db_cfg(self, firmware_type)
        end

        fw_mgmt:UpdateServiceUpdateServiceFinishReply(context.new(), system_id, firmware_type,
            self.upgrade_final_ret, parameters)
        clear_vrd_upgrade_resource(self)
        log:notice("[Vrd] upgrade finish end")
    end)
end

function upgrade_service_vrd:on_active_process(system_id, firmware_type)
    log:notice('[Vrd] active vrd start')
    update_active_status('VRD', firmware_type, 'Apply')

    local file_path, file_tab, sys_id
    if vos.get_file_accessible(defs.UPGRADE_VRD_VALID_FILE_DIR) then
        for _, file_name in pairs(utils_core.dir(defs.UPGRADE_VRD_VALID_FILE_DIR)) do
            -- file_name be like: HOST_<sys_id>_vrd.hpm
            file_tab = utils.string_split(file_name, '_', true)
            sys_id = tonumber(file_tab[2])
            if not sys_id and file_name == defs.VRD_HPM then
                -- 兼容老版本生效文件
                sys_id = 1
            elseif not sys_id then
                goto continue
            end
            file_path = defs.UPGRADE_VRD_VALID_FILE_DIR .. file_name
            fw_vrd_valid.vrd_valid_task(self, sys_id, file_path)
            ::continue::
        end
    else
        log:error('[Vrd] cache upgrade file not exist')
    end

    log:notice('[Vrd] active vrd finish')
    -- 等待10s，防止相应过快firmware_mgmt没有收到回应
    cmn.skynet.sleep(1000)
    fw_mgmt:FirmwareActiveFirmwareActiveActiveProcessReply(context.new(), system_id, firmware_type, defs.RET.OK)
end

function upgrade_service_vrd:is_firmware_upgrading()
    log:debug('[Vrd] is_vrd_upgrading = %s', self.is_vrd_upgrading)
    if self.is_vrd_upgrading then
        return true
    end
    return false
end

function upgrade_service_vrd:get_vrd_load()
    local collection = self.mcu_collection
    if not collection or not next(collection) then
        return false
    end
    for _, obj in pairs(self.mcu_collection) do
        if obj.mcu.BoardType == "BCU" then
            return obj:is_vrd_load()
        end
    end
    return false
end

return singleton(upgrade_service_vrd)